package org.hashdb.client.util.event;

import lombok.Getter;
import lombok.Setter;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * Date: 2024/1/9 16:00
 *
 * @author huanyuMake-pecdle
 * @version 0.0.1
 */
public abstract class AbstractEventEngin<E extends AbstractEventEngin<E>> implements IEventEngin<E> {
    protected final Map<Object, Set<Consumer<Object>>> listeners = new HashMap<>(3);
    protected final Set<Consumer<NotFountEvent>> notFoundListeners = new HashSet<>(3);
    @Getter
    protected final Consumer<NotFountEvent> defaultNotFoundListener;

    {
        @SuppressWarnings("unchecked")
        Set<Consumer<Object>> temp = (Set<Consumer<Object>>) ((Object) notFoundListeners);
        listeners.put(key.NOT_FOUND, temp);
    }

    /**
     * 如果为true, 则会在每次publish前触发 {@link key#BEFORE_PUBLISH} 事件
     */
    @Setter
    private boolean catchPublish = false;

    public AbstractEventEngin() {
        this((msg) -> System.err.println("not found listener. msg: '" + msg + "'"));
    }

    public AbstractEventEngin(Consumer<NotFountEvent> defaultNotFoundListener) {
        this.defaultNotFoundListener = defaultNotFoundListener;
    }

    @Override
    @SuppressWarnings("unchecked")
    public E register(Object eventKey, Consumer<Object> listener) {
        if (eventKey == key.NOT_FOUND) {
            return registerNotFound(((Consumer<NotFountEvent>) ((Object) listener)));
        }
        listeners.computeIfAbsent(eventKey, k -> new HashSet<>()).add(listener);
        return (E) this;
    }

    public BatchEventRegister listen(Object... eventKeys) {
        return new BatchEventRegister(eventKeys);
    }

    public BatchEventUnregister unListen(Object... eventKeys) {
        return new BatchEventUnregister(eventKeys);
    }

    @Override
    @SuppressWarnings("unchecked")
    public E unregister(Object eventKey, Consumer<Object> listener) {
        if (eventKey == key.NOT_FOUND) {
            return unregisterNotFound(((Consumer<NotFountEvent>) ((Object) listener)));
        }
        Set<?> listenerSet = listeners.get(eventKey);
        if (listenerSet != null) {
            listenerSet.remove(listener);
        }
        return (E) this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public E unregisterNotFound(Consumer<NotFountEvent> listener) {
        notFoundListeners.remove(listener);
        return (E) this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public E registerNotFound(Consumer<NotFountEvent> listener) {
        notFoundListeners.remove(defaultNotFoundListener);
        notFoundListeners.add(listener);
        return (E) this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public E publishAsync(Object eventKey, Object message) {
        if (eventKey != key.BEFORE_PUBLISH) {
            publishBeforePublish();
        }
        var listenerSet = listeners.get(eventKey);
        if (listenerSet == null) {
            NotFountEvent notFountEvent = new NotFountEvent(eventKey, message);
            notFoundListeners.parallelStream().forEach(listener -> listener.accept(notFountEvent));
            return (E) this;
        }
        listenerSet.parallelStream().forEach(listener -> listener.accept(message));
        return (E) this;
    }

    @Override
    public void publishBeforePublish() {
        if (!catchPublish) {
            return;
        }
        publish(key.BEFORE_PUBLISH, null);
    }

    @Override
    public E publish(Object eventKey) {
        return publish(eventKey, null);
    }

    @Override
    @SuppressWarnings("unchecked")
    public E publish(Object eventKey, Object message) {
        if (eventKey != key.BEFORE_PUBLISH) {
            publishBeforePublish();
        }
        var listenerSet = listeners.get(eventKey);
        if (listenerSet == null) {
            NotFountEvent notFountEvent = new NotFountEvent(eventKey, message);
            for (Consumer<NotFountEvent> notFoundListener : notFoundListeners) {
                notFoundListener.accept(notFountEvent);
            }
            return (E) this;
        }
        for (Consumer<Object> listener : listenerSet) {
            listener.accept(message);
        }
        return (E) this;
    }

    public enum key {
        BEFORE_PUBLISH,
        UNCATCH,
        NOT_FOUND
    }

    public class BatchEventRegister {

        private final Object[] keys;

        public BatchEventRegister(Object... eventKeys) {
            keys = eventKeys;
        }

        @SuppressWarnings("unchecked")
        public E register(Consumer<Object> listener) {
            for (Object key : keys) {
                AbstractEventEngin.this.register(key, listener);
            }
            return (E) AbstractEventEngin.this;
        }
    }

    public class BatchEventUnregister {
        private final Object[] keys;

        public BatchEventUnregister(Object... eventKeys) {
            keys = eventKeys;
        }

        @SuppressWarnings("unchecked")
        public E unregister(Consumer<Object> listener) {
            for (Object key : keys) {
                AbstractEventEngin.this.unregister(key, listener);
            }
            return (E) AbstractEventEngin.this;
        }
    }
}
